from vtk import *
import math
import copy
import networkx as nx
import random

class FormalConcept:
    def __init__(self,id):
        self.id = id
        self.extent = []
        self.intent = []
        self.parents = []
        self.children = []
        self.stability = 0
        self.type = None
        self.level = 0

    def SetExtent(self,extent):
        self.extent = extent

    def SetIntent(self,intent):
        self.intent = intent

    def GetExtent(self):
        return self.extent

    def GetIntent(self):
        return self.intent

    def GetCleanIntent(self,output='a'):
        rs = copy.copy(self.intent)
        for c in self.parents:
            for a in c.GetIntent():
                if a in rs:
                    del rs[rs.index(a)]
        if output == 'a':
            return rs
        elif output == 's':
            op = ''
            for i in rs:
                op += ','+i
        return op[1:]

    def GetCleanExtent(self,output='a'):
        rs = copy.copy(self.extent)
        for c in self.children:
            for a in c.GetExtent():
                if a in rs:
                    del rs[rs.index(a)]
        if output == 'a':
            return rs
        elif output == 's':
            op = ''
            for i in rs:
                op += ','+i
        return op[1:]

    def GetId(self):
        return self.id

    def SetChildren(self,concepts):
        self.children = concepts

    def GetChildren(self):
        return self.children

    def SetParents(self,concepts):
        self.parents = concepts

    def GetParents(self):
        return self.parents

    def GetSupport(self):
        return len(self.extent)

    def __str__(self):
        return "Muajaja!"

    def Summary(self):
        s = 'Concept ID: {} \n'.format(self.id)
        s = 'Support: {} \n'.format(len(self.extent))
        s += 'Intent: {} \n'.format(self.intent)
        s += 'Stability {} \n'.format(self.stability)
        return s

class ConceptLattice:
	DENSITY = 0
	MEAN_ATTRIBUTES = 1
	TIME_USED = 2
	
	def __init__(self):
		self.concepts = {}
		self.top = None
		self.bottom = None
		self.levels = {}
		self.features = {}
	
	def SetLatticeFeature(self,name,value):
		self.features[name] = value

	def GetConceptsByLevel(self,level):
		return self.levels[level]
	
	def Concept(self,key):
		if key not in self.concepts:
			self.concepts[key] = FormalConcept(key)
		return self.concepts[key]
	
	def SetConceptStability(self,key,stability):
		self.concepts[key].stability = stability
	
	def SetConceptLevel(self,key,lev):
		self.concepts[key].level = lev
		if lev not in self.levels:
			self.levels[lev] = []
		self.levels[lev].append(self.concepts[key])
	
	def SetConceptExtent(self,key,objs):
		self.concepts[key].SetExtent(objs)
	
	def SetConceptIntent(self,key,ats):
		self.concepts[key].SetIntent(ats)
	
	def SetConceptParents(self,key,pars):		
		self.concepts[key].SetParents([self.Concept(i) for i in pars])
	
	def SetConceptChildren(self,key,chs):
		self.concepts[key].SetChildren([self.Concept(i) for i in chs])
	
	def SetConcepType(self,key,type):
		self.concepts[key].type = type
		if type == 'top':
			self.top = self.concepts[key]
		elif type == 'bottom':
			self.bottom = self.concepts[key]

	def GetNumberOfConcepts(self):
		return len(self.concepts)
	
	def GetKeys(self):
		return self.concepts.keys()

	def GetConcept(self,key):
		return self.concepts[key]

	def GetNumberOfLevels(self):
		return len(self.levels)
	

'''
Reads a lattice from Coron output.
'''
def readLattice(lattice):
	f = open(lattice, "r")
	__lineN = 3
	counter = 0
	CL = ConceptLattice()
	for line in f:
		if not (line.startswith("#") or line.startswith("Top") or line.startswith("Bottom") or line.startswith("\n") or "generators" in line):
			counter += 1
			if counter%__lineN==1: # Info intent extent
				# TYPE
				type = line[line.index("type=")+5:line.index(", level")]
				# Intent
				intent = line[line.index("{")+1:line.index("}")]
				if intent == '':
					intent = 'none'
				#print 'intent',intent
				#print intent
				if type == "inner" or type=='bottom':
					CL.Concept(intent)
					# Extent
					CL.SetConceptExtent(intent,[o.strip() for o in line[line.index("[")+1:line.index("]")].split(",")])
					# Intent
					CL.SetConceptIntent(intent,[i.strip() for i in intent.split(",")])
					# Level
					CL.SetConceptLevel(intent,int(line[line.index("level=")+6:line.index("stability=")].replace(", ","")))
					# Stability
					stab = line[line.rfind("=")+1:]
					stab = float(stab.replace("]\n",""))
					CL.SetConceptStability(intent,stab)
				elif type == "top":
					intent = "none"
					CL.Concept(intent)
					CL.SetConceptExtent(intent,[o.strip() for o in line[line.index("[")+1:line.index("]")].split(",")])
					CL.SetConceptStability(intent,1.0)
					CL.SetConceptLevel(intent,0)
			CL.SetConcepType(intent,type)
			
			if counter%__lineN==2: # Information of parents
				parent = line[line.index("[")+1:line.index("]")]
				parent = parent.split("}, ")
				ents = []
				for c in range(len(parent)):
					par = parent[c].replace("{","").replace("}","").strip()
					if par != "":
						ents.append(par)
				CL.SetConceptParents(intent,ents)
			
			if counter%__lineN==0: # Information of children
				children = line[line.index("[")+1:line.index("]")]
				children = children.split("}, ")
				ents = []
				for c in range(len(children)):
					ch = children[c].replace("{","").replace("}","").strip()
					if ch != "":
						ents.append(ch)
				CL.SetConceptChildren(intent,ents)
		
		else:
			if line.startswith("# Number of attributes in average:"):
				CL.SetLatticeFeature(CL.MEAN_ATTRIBUTES,float(line.replace("# Number of attributes in average:","").replace("\n","").strip().replace(",",".")))
			if line.startswith("# Density:"):
				CL.SetLatticeFeature(CL.DENSITY,float(line.replace("# Density:","").replace("\n","").replace("%","").strip().replace(",",".")))
			if line.startswith("# Summary:"):
				CL.SetLatticeFeature(CL.TIME_USED,float(line.replace("# Summary:","").replace("sec.","").replace("\n","").strip().replace(",",".")))
	f.close()
	return CL


'''
    Reads a lattice from Coron output.
    '''
def readLatticeGML(lattice):
    f = open(lattice, "r")
    CL = ConceptLattice()

    g = nx.read_gml(lattice)

    for n in g.nodes():
        intent = g.node[n]['intt'].split(',')
        print intent
        CL.Concept(n)
        CL.SetConceptExtent(n,intent)
        CL.SetConceptStability(n,0)
        CL.SetConceptIntent(n,intent)
        if 'support' in g.node[n]:
            CL.SetConceptLevel(n,g.node[n]['support'])
    children = {}
    for e in g.edges():
        if e[0] not in children:
            children[e[0]] = []
        children[e[0]].append(e[1])
    for e in children:
        print e,children[e]
        CL.SetConceptChildren(e,children[e])
    return CL
        

class GraphicLattice:
    def __init__(self):
    # ARRAY WITH LABELS TO DISPLAY ON THE GRAPH
        self.vertex_id_array = vtkIdTypeArray() 
    # ARRAY WITH LABELS TO DISPLAY ON THE GRAPH
        self.vertex_label_array = vtkStringArray() 
    # ARRAY WITH LABELS TO DISPLAY ON THE GRAPH
        self.intent_array = vtkStringArray() 
    # ARRAY WITH LABELS TO DISPLAY ON THE GRAPH
        self.extent_array = vtkStringArray() 
    # ARRAY WITH LABELS TO DISPLAY ON THE GRAPH
        self.extint_array = vtkStringArray() 
    # COORDINATES ARRAY
        self.points = vtkPoints()	
    # STABILITY ARRAY
        self.stab_vertex_array = vtkFloatArray() 
    # SUPPORT ARRAY
        self.sup_vertex_array = vtkIntArray()    
    # SIZE ARRAY
        self.size_vertex_array = vtkFloatArray() 	
    # EDGES ARRAY
        self.edge_id_array = vtkIdTypeArray()
    # INDEX CONCEPT -> NODE
        self.vertex = {} 
    # GRAPH STRUCTURE WHICH HANDLES EVERYTHING TOGETHER
        self.graph=vtkMutableDirectedGraph()
    
    
    @classmethod
    def fromConceptLattice(cls,lattice):
        gl = cls()
        gl.create_lattice_vertices(lattice)
        gl.create_lattice_edges(lattice)
        # ASSIGN GRAPH DATA TO THE GRAPH
        gl.compile_graph()
        return gl

    '''
        ALL THE DATA ARRAYS ARE COMPILED WITHIN THE LATTICE
        THESE DATA ARRAYS CONTAIN LABELS, COORDINATES, NAMES, SIZES, ETC
        AND SHOULD BE PREVIOUSLY CALCULATED IN CREATE_LATTICE_VERTICES
    '''
    def compile_graph(self):
        self.graph.GetVertexData().SetPedigreeIds( self.vertex_id_array )
        self.graph.GetVertexData().AddArray(self.stab_vertex_array)
        self.graph.GetVertexData().AddArray(self.sup_vertex_array)
        self.graph.GetVertexData().AddArray(self.vertex_label_array)
        self.graph.GetVertexData().AddArray(self.intent_array)
        self.graph.GetVertexData().AddArray(self.extint_array)
        self.graph.GetVertexData().AddArray(self.extent_array)
        self.graph.GetVertexData().AddArray(self.size_vertex_array)
        self.graph.GetEdgeData().SetPedigreeIds( self.edge_id_array )
        self.graph.SetPoints(self.points)

    def create_lattice_vertices(self,lattice):
        print "Creating Vertices..."
        self.vertex_id_array.SetName("id")
        self.vertex_id_array.SetNumberOfTuples(lattice.GetNumberOfConcepts())

        self.vertex_label_array.SetName("names")
        self.vertex_label_array.SetNumberOfTuples(lattice.GetNumberOfConcepts())

        self.intent_array.SetName("intent")
        self.intent_array.SetNumberOfTuples(lattice.GetNumberOfConcepts())
        self.extent_array.SetName("extent")
        self.extent_array.SetNumberOfTuples(lattice.GetNumberOfConcepts())
        self.extint_array.SetName("extint")
        self.extint_array.SetNumberOfTuples(lattice.GetNumberOfConcepts())

        self.stab_vertex_array.SetName("stability")
        self.stab_vertex_array.SetNumberOfTuples(lattice.GetNumberOfConcepts())
        
        self.sup_vertex_array.SetName("support")
        self.sup_vertex_array.SetNumberOfTuples(lattice.GetNumberOfConcepts())
        
        self.size_vertex_array.SetName("size")
        self.size_vertex_array.SetNumberOfTuples(lattice.GetNumberOfConcepts())
        
        
        
        # MAINTAIN COORDINATES OFFSET INFORMATION 
        indexes = {}
        # CREATE VERTEX
        for e in lattice.GetKeys():
            d = lattice.GetConcept(e)
            if d.level not in indexes:
                indexes[d.level] = 1
            if True:
            # VERTEX
                self.vertex[e] = self.graph.AddVertex()
                self.vertex_id_array.SetValue(self.vertex[e],self.vertex[e])
                
            # CALCULATE COORDINATES
                levels = lattice.GetNumberOfLevels()
                ylim = 200 
                y = ylim-(ylim*d.level/levels) + (random.random()-0.5)*10
                xlim = 200
                x = -1*xlim/2 + xlim * (indexes[d.level])/(len(lattice.GetConceptsByLevel(d.level))+1) + (random.random()-0.5)*10	

            #ASSIGN VALUES
                self.points.InsertNextPoint(x,y,0.0)

            # WHERE YOU MODIFY THE LABELS OF THE CONCEPTS
                #self.vertex_label_array.SetValue(self.vertex[e],g.GetCle)
                if len(d.GetIntent()) > 0:
                    self.size_vertex_array.SetValue(self.vertex[e],5)
                else:
                    self.size_vertex_array.SetValue(self.vertex[e],1)
                if lattice.GetConcept(e).GetSupport() != 0:
                    #print e,lattice.GetConcept(e).GetSupport()
                    self.size_vertex_array.SetValue(self.vertex[e],math.log(lattice.GetConcept(e).GetSupport()))
                else:
                    self.size_vertex_array.SetValue(0.1)
                self.intent_array.SetValue(self.vertex[e],str(lattice.GetConcept(e).GetCleanIntent('s')))
                self.extent_array.SetValue(self.vertex[e],str(lattice.GetConcept(e).GetCleanExtent('s')))
                self.extint_array.SetValue(self.vertex[e],str(str(d.GetCleanExtent(output='s'))+'\n'+str(d.GetCleanIntent(output='s'))))
                self.stab_vertex_array.SetValue(self.vertex[e],d.stability)
                self.sup_vertex_array.SetValue(self.vertex[e],d.GetSupport())
            indexes[d.level] += 1
    #return vertex, points, vertex_id_array,vertex_label_array,stab_vertex_array,sup_vertex_array,intent_array,size_vertex_array


    def create_lattice_edges(self,lattice):
        print "Creating Edges..."
        self.edge_id_array.SetName("id")
        # CREATE EDGES
        d = 0
        for key in lattice.GetKeys():
            if key in self.vertex:
                for concept2 in lattice.GetConcept(key).GetChildren():
                    e = concept2.id
                    if e in self.vertex:
                        a = self.graph.AddGraphEdge(self.vertex[key],self.vertex[e])
                        #print a.GetId()
                        self.edge_id_array.InsertValue(a.GetId(),a.GetId())
                        d += 1

